【AWS】Internal Port Concentrator パターン(仮)でプライベートサブネットへのアクセスを考える
こんにちは、植木和樹です。本日はAWS VPC環境において、グローバルIPを持たないサーバへインターネットからアクセスする手段について取り上げます。
本日の課題
VPCでサブネットを作る際には、大きく2つのサブネットを作成するかと思います。一つはInternetGatewayを持ち、EIPを割り当てることでインターネットとの通信を行うサーバを配置する「パブリックサブネット」、もう一つはNATサーバを介してのみインターネットと通信することができる「プライベート(プロテクテッド)サブネット」です。
プライベートサブネットに配置するサーバには「インターネットから接続する必要がない」サーバ、例えばバッチ処理しか行わないサーバや、ELBの配下に入るWebサーバなどがあります。
ただ「インターネットから接続する必要がない」というのは、あくまで提供しているサービスを利用する立場からみた話で、運用が始まると「開発目的でMySQLにつなぎたい」とか「動作確認のためELB配下の特定のWebサーバにだけアクセスしたい」といった開発や保守担当者からの要望がでてくるかと思います。
今回はそういった要望をどのように実現するかを考えてみました。
NATサーバを使った「Internal Port Concentrator」CDPパターン
NATサーバはプライベートサブネットにあるサーバが、VPC外のサービス(NTPやyumリポジトリ、S3バケットも)へアクセスするために使用する「内→外」方向の通信のために使用しています。Amazonが提供しているAMIから起動したNATサーバの場合、NAT機能はiptablesで実現されています。
このNATサーバ(iptables)に「内→外」だけじゃなく、「外→内」の通信も仲介(ポートマッピングによる転送)をしてもらえれば、インターネットから特定サーバの特定ポートへのアクセスができそうですね。「VPCプライベートサブネット内にあるサーバのポートをNATサーバに集約する」ことから「Internal Port Concentrator パターン」と名付けてみました。
名前の由来は、複数のシリアルポートをまとめTCP/IP経由でのアクセスを可能にする「ターミナル・コンセントレータ(ターミナル・サーバ)」というハードウェアからヒントをいただいてます。
VPC環境を構築する
まずは試験環境となるVPCを構築しましょう。VPC環境の構築を一からやるのは大変なのでCloudFormationを使います。
マネージメントコンソールにログインが済んだ状態でAWSが提供しているCloudFormationテンプレート集のページにアクセスし、"multi-tier-web-app-in-vpc.template"をページ内検索します。見つかったらページ右側にある"Launch Stack"ボタンをクリックするとCloudFormationの "Create Stack" 画面が開きますので画面の指示に従って(パラメータとしてkeypair名が必要です)スタックを作成しましょう。これでVPCやサブネット、NATサーバやいくつかのELBとEC2インスタンスが作成されます。
このCloudFormationではサブネットを単一AZにしか作らないので、マネージメントコンソールを使って、別AZにもパブリックとプライベート計2つのサブネットを作っておきましょう。
次にRDSも作成します。先ほど同様CloudFormationテンプレート集のページから"VPC_RDS_DB_Instance.template"を使ってスタックを作成します。なおこのテンプレートはVpcID と RDSを配置するSubnetID(カンマ区切りで複数指定必須)がパラメータとして必要です。事前にご自身の環境のIDをメモしておきましょう。
NATサーバにポートマッピングを設定する
NATサーバはVPC構築で作成されたEC2インスタンスをそのまま使用します。NATサーバにsshでログインしてポートマッピングを設定していきます。
NAT機能はOS起動処理の最後に/etc/rc.localからシェルスクリプトを呼び出すことで行われています。
/etc/rc.local
#!/bin/sh # # This script will be executed *after* all the other init scripts. # You can put your own initialization stuff in here if you don't # want to do the full Sys V style init stuff. touch /var/lock/subsys/local # Configure PAT /usr/local/sbin/configure-pat.sh
シェルスクリプトの最後で実行している/usr/local/sbin/configure-pat.shが実際にNATを実現しているシェルスクリプトです。ざっくり何をしているか見てみましょう。
- ifconfig で eth0 のMACアドレスを調べる
- インスタンスメタデータとMACアドレスからVPCのIPアドレスレンジ(10.0.0.0/16)を入手する
- カーネルパラメータ net.ipv4.ip_forward に 1 を設定してIPv4のフォワードを有効にする
- カーネルパラメータ net.ipv4.ICMP redirect に 0 設定しICMP リダイレクトメッセージをプライベートサブネットのホストへ送信しないようにする
- iptablesでVPCのアドレスレンジからの通信にはNAT変換を行う
【参考】Linux Advanced Routing & Traffic Control HOWTO
/usr/local/sbin/configure-pat.sh
#!/bin/bash # Configure the instance to run as a Port Address Translator (PAT) to provide # Internet connectivity to private instances. # set -x echo "Determining the MAC address on eth0" ETH0_MAC=`/sbin/ifconfig | /bin/grep eth0 | awk '{print tolower($5)}' | grep '^[0-9a-f]\{2\}\(:[0-9a-f]\{2\}\)\{5\}$'` if [ $? -ne 0 ] ; then echo "Unable to determine MAC address on eth0" | logger -t "ec2" exit 1 fi echo "Found MAC: ${ETH0_MAC} on eth0" | logger -t "ec2" VPC_CIDR_URI="http://169.254.169.254/latest/meta-data/network/interfaces/macs/${ETH0_MAC}/vpc-ipv4-cidr-block" echo "Metadata location for vpc ipv4 range: ${VPC_CIDR_URI}" | logger -t "ec2" VPC_CIDR_RANGE=`curl --retry 3 --retry-delay 0 --silent --fail ${VPC_CIDR_URI}` if [ $? -ne 0 ] ; then echo "Unable to retrive VPC CIDR range from meta-data. Using 0.0.0.0/0 instead. PAT may not function correctly" | logger -t "ec2" VPC_CIDR_RANGE="0.0.0.0/0" else echo "Retrived the VPC CIDR range: ${VPC_CIDR_RANGE} from meta-data" |logger -t "ec2" fi echo 1 > /proc/sys/net/ipv4/ip_forward && \ echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects && \ /sbin/iptables -t nat -A POSTROUTING -o eth0 -s ${VPC_CIDR_RANGE} -j MASQUERADE if [ $? -ne 0 ] ; then echo "Configuration of PAT failed" | logger -t "ec2" exit 0 fi echo "Configuration of PAT complete" |logger -t "ec2" exit 0
今回設定しようとしているポートマッピングの対象は数が多いのでシェルスクリプトに直書きせず、/etc/sysconfig/iptablesで管理したいと思います。そこでいったん現在の設定を/etc/sysconfig/iptablesに保存してから、シェルを少々修正しNAT(MASQUERADE)している処理を削ってしまいましょう。
# service iptables save # cat /etc/sysconfig/iptables
# Generated by iptables-save v1.4.18 on Sat Jul 6 09:37:51 2013 *nat :PREROUTING ACCEPT [1:64] :INPUT ACCEPT [1:64] :OUTPUT ACCEPT [19:1466] :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 10.0.0.0/16 -o eth0 -j MASQUERADE COMMIT # Completed on Sat Jul 6 09:37:51 2013
# vi /usr/local/sbin/configure-pat.sh
echo 1 > /proc/sys/net/ipv4/ip_forward && \ echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects
インターネットからプライベートサーバへ通信させたいポートは次の3つにしました。システム構築後に自社開発部門からWebサーバやMySQLサーバにログインして、アプリケーションの動作検証や障害原因の調査をすることを想定しています。
- インターネットからプライベートサブネットにあるEC2(Webサーバ)へのhttpアクセス
- インターネットからプライベートサブネットにあるEC2(Webサーバ)へのsshアクセス
- インターネットからプライベートサブネットにあるRDS(MySQLサーバ)へのmysqlアクセス
NATサーバの特定のポートにアクセスした時に、プライベートサブネットにあるサーバの、特定ポートにフォワードされるようにします。対応付けは以下のようにしました。
アクセスするIPアドレス | アクセスするポート | フォワード先IPアドレス | フォワード先ポート |
---|---|---|---|
NATサーバのEIP | 50001 | WebサーバのプライベートIP | http(tcp/80) |
NATサーバのEIP | 50002 | WebサーバのプライベートIP | ssh(tcp/22) |
NATサーバのEIP | 50003 | RDS(MySQL) EndPointのプライベートIP | mysql(tcp/3306) |
iptablesでは以下のコマンドで設定することができます。NATサーバのアドレスはEIPアドレスでなくプライベートアドレス(10.0.0.155)で指定しているという点に注意してください。また--to-destinationにはホスト名が指定できないため、事前にdigコマンドでRDS EndPointのIPアドレスを調べておきます。
# iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50001 -j DNAT --to-destination 10.0.1.24:80 # iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50002 -j DNAT --to-destination 10.0.1.24:22 # iptables -t nat -A PREROUTING -p tcp -d 10.0.0.155 --dport 50003 -j DNAT --to-destination 10.0.11.147:3306
うまく登録されたか確認してみましょう。
# iptables -t nat -L Chain PREROUTING (policy ACCEPT) target prot opt source destination DNAT tcp -- anywhere 10.0.0.155 tcp dpt:50001 to:10.0.1.24:80 DNAT tcp -- anywhere 10.0.0.155 tcp dpt:50002 to:10.0.1.24:22 DNAT tcp -- anywhere 10.0.0.155 tcp dpt:50003 to:10.0.11.147:3306 Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 10.0.0.0/16 anywhere
問題ないようなので、次回 iptables サービス起動時にこのルールが読み込まれるようsaveしておきます。
# service iptables save # chkconfig --list iptables iptables 0:off 1:off 2:on 3:on 4:on 5:on 6:off
接続確認をする前にセキュリティグループを変更しておきましょう(Inbound/Outboundとも)。なお接続元は実際のクライアントのIPアドレスでなく、NATサーバに対して許可している点に注意してください。
接続元IPアドレス または セキュリティグループ | 接続先セキュリティグループ | 接続先ポート |
---|---|---|
インターネット(自社または開発委託業者のグローバルIPアドレス) | NATサーバ | tcp/50001〜50003 |
NATサーバ | Webサーバ | http(tcp/80) |
NATサーバ | Webサーバ | ssh(tcp/22) |
NATサーバ | RDS(MySQL) | mysql(tcp/3306) |
接続確認する
HTTP(tcp/50001 -> tcp/80)
ブラウザでNATサーバの50001番ポートにアクセスします(http://54.249.XXX.XXX:50001/)。正しく設定できていればWebサーバのページが表示されます。
SSH(tcp/50002 -> tcp/22)
ローカルPCからNATサーバの50002番ポートにsshでアクセスします。正常にWebサーバにログインできコマンドプロンプトが表示されれば成功です。
$ ssh -i ~/.ssh/my_keypair.pem [email protected] -p 50002
MySQL(tcp/50003 -> tcp/3306)
mysqlコマンドでNATサーバの50003番ポートに接続します。CloudFormationで作った場合の初期ユーザとパスワードは admin/admin です。ログインできれば成功です。
$ mysql -u admin -p -h 54.249.XXX.XXX -P 50003 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 54 Server version: 5.5.31-log Source distribution Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
Internal Port Concentrator 使用時に注意する点
ポートマッピングを使ってインターネットからプライベートサブネットのサーバに接続することができました。あとは必要な数だけマッピングをコマンドで追加しservice iptables saveすれば良いでしょう。ただ本運用にのせるには、まだいくつか課題が残っています。
NATサーバにアクセスできる外部IPアドレスを極力しぼること
本来であればプライベートサブネットのサーバにはインターネットからのアクセスが行われないはずです。そこに「穴」を開けています。セキュリティを少しでも向上させるためにも接続元は制限しましょう。具体的にはNATサーバのセキュリティグループのInboud(50001-50003)へアクセスできるIPアドレスを限定すれば良いでしょう。
AutoScalingなどによるプライベートIPアドレス変更の反映
ELB配下のWebサーバはAutoScaling構成にすることが多いと思います。そうするとEC2インスタンスが入れ替わる(増減する)たびにマッピング情報をメンテナンスしなければいけません。aws-apitoolsなどを使ってこの辺は自動化を検討したいところですね。
RDSフェイルオーバーの考慮
Multi-AZのRDSの場合、フェイルオーバーするとIPアドレスが変わります。iptablesにはEndPointでなくIPアドレスを直接指定しるため、フェイルオーバーした場合は改めてEndPointのIPアドレスを調べてiptablesを設定しなおす、といった運用の手間が発生します。ここも自動化しておきたいですね。
NATのポートと実際のポートの管理
正直NATサーバの50001番ポートがWebサーバの80番ポートに繋がっている・・・というのは直感的ではありません。月日が経つにつれ忘れ去られてしまわないように、環境定義書等のドキュメントにはちゃんと記述しておきましょう。そして運用ドキュメントは正しく情物一致しているか定期的に棚卸しましょう!
まとめ
今回はVPC構築後、後々になってメンテナンス用通信経路が必要になった場合、というものを想定し解決策を検討してみました。
いままでClassic-EC2環境では、PublicDNSが自動的に割り当てられていたために、インターネットからAWS内へのメンテナンス用アクセスというものはそれほど気にしなくても良い状況でした。しかしVPCが登場し次々に新しい機能が実装されていく昨今、今後はVPC導入を検討されるお客様が増えてくるかと思います。そして環境設計の際はどうしても外部向けサービス提供が視点になってしまうため、メンテナンス用通信経路が見落とされがちです。
本来であれば構築の段階から開発・保守運用に必要なプライベートサブネットへの安全なアクセスというものを考え、必要に応じてVPNを用意する(